package ordonnanceur;

import generationTache.IGenerationTache;

import java.io.FileNotFoundException;
import java.util.ArrayList;

import outils.ManipulationFichier;
import outils.MathTR;

/**
 *  Permet d'appliquer aux taches le couple d'ordonnanceur EDF/BG
 * @author nicolas rault et matthieu allon
 *
 */
public class RM_BG {

	private ArrayList<IGenerationTache> listeTacheP;
	private ArrayList<IGenerationTache> listeTacheAp;
	private ArrayList<String> listeKiwi = new ArrayList<String>();	
	private ManipulationFichier<IGenerationTache> outils;
	private int ppcm;
	private int duration;
	
	/**
	 * Constructeur : permet de charger les taches periodiques et aperiodiques a partir de fichiers deja existant.
	 */
	public RM_BG()
	{
		lectureFichier();
		ppcm = 0;
	}
	
	/**
	 * Permet de recuperer les taches periodiques et aperiodiques des fichiers existants (TAcheP.txt et TacheAp.txt)
	 */
	private void lectureFichier()
	{
		listeTacheAp = new ArrayList<IGenerationTache>();		
		listeTacheP = new ArrayList<IGenerationTache>();
		outils = new ManipulationFichier<IGenerationTache>();
		try {
			listeTacheAp = outils.read("TacheAp.txt");
			listeTacheP = outils.read("TacheP.txt");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Permet de tester l'ordonnancement du systeme
	 * @return "Le système est ordonnançable" si le test suffisant est verifie
	 * @return "Le système n'est pas ordonnançable" si les tests necessaire et suffisant ne sont pas verifies
	 * @return "On ne sait pas si le système est ordonnançable" si le test suffisant n'est pas verifie mais que le test necessaire est verfie
	 */
	public String testOrdonnancement()
	{
		if (testSuffisant())
		{
			return "Le système est ordonnançable";
		}
		else
		{
			if (testNecessaire())
			{
				return "On ne sait pas si le système est ordonnançable";
			}
			return "Le système n'est pas ordonnançable";
		}
	}
	
	/**
	 * Permet d'effectuer le test suffisant sur les taches
	 * @return vrai si le test est verifie, faux sinon.
	 */
	private boolean testSuffisant()
	{
		int sommeT = sommeCiPi();
		int nbTache = listeTacheP.size();
		int UborneRm = (int) (nbTache*(Math.pow(2, 1/nbTache)-1));
		if (sommeT <= UborneRm)
		{
			return true;
		}
		return false;
	}
	
	/**
	 * Permet d'effectuer le test necessaire sur les taches
	 * @return vrai si le test est verifie, faux sinon.
	 */
	private boolean testNecessaire()
	{		
		int U = sommeCiPi();
		if (U <= 1)
		{
			return true;
		}
		return false;
	}
	
	/**
	 * Permet de calculer la somme des Ci/Pi
	 * @return la somme des Ci/Pi
	 */
	private int sommeCiPi()
	{
		int U =0;
		for(int i =0;i<listeTacheP.size();i++)
		{
			U += listeTacheP.get(i).getCi() / listeTacheP.get(i).getPi();
		}
		return U;
	}
	
	public void ordonnancement()
	{
		//Calcul du PPCM pour les taches périodiques
		calculPPCM();
		
		//generation de la liste des taches pour kiwi
		generationListKiwi();
	}
	
	/**
	 * Permet de calculer le ppcm par rapport aux Di des taches periodiques
	 */
	private void calculPPCM()
	{
		//Calcul du PPCM		
		if(listeTacheP.size() > 2)
		{
			ppcm = MathTR.calculePPCM(listeTacheP.get(0).getPi(), listeTacheP.get(1).getPi());
			for(int i = 2; i<listeTacheP.size(); i++)
			{
				ppcm = MathTR.calculePPCM(ppcm, listeTacheP.get(i).getPi());
			}
		}
		else
		{
			if(listeTacheP.size() == 2)
			{
				ppcm = MathTR.calculePPCM(listeTacheP.get(0).getPi(),listeTacheP.get(1).getPi());
			}
			else
			{
				ppcm = listeTacheP.get(0).getPi();
			}
		}
	}
	
	/**
	 * Permet de trier les taches periodiques suivant leur periode (Pi)
	 */
	private void triTacheP()
	{
		boolean fini = false;
		while(!fini){
			fini = true;
			for(int i =0;i<listeTacheP.size();i++)
			{
				if((i+1)<listeTacheP.size()){
					if(listeTacheP.get(i).getPi() > listeTacheP.get(i+1).getPi()){
						IGenerationTache Tmp = listeTacheP.get(i);
						listeTacheP.remove(i);
						listeTacheP.add(i+1,Tmp);
						fini = false;
					}
				}
			}
		}
	}
	
	/**
	 * Permet de trier les taches aperiodiques suivant leur reveil (ri)
	 */
	private void triTacheAp()
	{
		if(listeTacheAp.size() >0)
		{
			boolean fini = false;
			while(!fini){
				fini = true;
				for(int i =0;i<listeTacheAp.size();i++)
				{
					if((i+1)<listeTacheAp.size()){
						if(listeTacheAp.get(i).getRi() > listeTacheAp.get(i+1).getRi()){
							IGenerationTache Tmp = listeTacheAp.get(i);
							listeTacheAp.remove(i);
							listeTacheAp.add(i+1,Tmp);
							fini = false;
						}
					}
				}
			}
		}
	}
	
	/**
	 * Permet de generer la liste necessaire au parseur du fichier kiwi
	 */
	private void generationListKiwi()
	{		
		System.out.println("Début du calcul de l'ordonnancement.");
		//Tri des taches pour l'ordonnacement RM
		triTacheP();
		//Tri des taches apériodiques suivant leur date de réveil
		triTacheAp();
		//Toutes les Taches Periodiques sont pretes, et leur durée au max
		for(int i = 0; i<listeTacheP.size(); i++)
		{
			listeTacheP.get(i).setActive(true);
			listeTacheP.get(i).setDuree(listeTacheP.get(i).getCi());
		}
		//Toutes les Taches Apériodiques ont leur durée de connu
		for(int i = 0; i<listeTacheAp.size(); i++)
		{
			listeTacheAp.get(i).setDuree(listeTacheAp.get(i).getCi());
		}
		int i = 0;
		int tachePrecedente = -1;
		boolean tachePrecedenteActive = false;
		boolean isPeriodique = true;
		int nbPreemption = 0;
		int nbChangementContexte = 0;
		int nbViolationEcheance = 0;
		int compteur = 100;
		boolean allApDone = false;
		while(i<ppcm && compteur>0)
		{
			int a =0;
			if(i != 0)
			{
				//On vérifie si des taches Periodiques doivent passer en active et on remet a jour leur duree
				for(a = 0; a<listeTacheP.size() && i != 0; a++)
				{
					if(i%listeTacheP.get(a).getPi() == 0)
					{
						listeKiwi.add("STOP "+a);
						if(listeTacheP.get(a).getDuree() != 0)
						{
							nbViolationEcheance++;
						}
						listeTacheP.get(a).setDuree(listeTacheP.get(a).getCi());
						listeTacheP.get(a).setActive(true);
					}
				}
			}
			//On fait la meme chose pour les taches Apériodiques
			for(a = 0; a<listeTacheAp.size(); a++)
			{
				if(i == listeTacheAp.get(a).getRi())
				{
					listeTacheAp.get(a).setActive(true);
				}
			}
			
			int tache = -1;
			boolean find = false;
			//On vérifie qu'une tache périodique soit prete
			for(a = 0; a<listeTacheP.size() && !find; a++)
			{
				if(listeTacheP.get(a).getActive())
				{
					listeTacheP.get(a).setDuree(listeTacheP.get(a).getDuree()-1);
					if(listeTacheP.get(a).getDuree() == 0)
					{
						listeTacheP.get(a).setActive(false);
					}
					tache = a;
					find = true;
					//On vérifie si la tache précédemment traitée était une apériodique
					if(!isPeriodique)
					{
						nbChangementContexte++;
						isPeriodique = true;
					}
				}
			}
			//Si aucune tache périodique n'est prete, on regarde les taches Apériodiques
			if(tache == -1)
			{
				for(a = 0;a<listeTacheAp.size() && !find; a++)
				{
					if(listeTacheAp.get(a).getActive())
					{
						if(listeTacheAp.get(a).getDuree() == listeTacheAp.get(a).getCi())
						{
							//Premiere fois que la tache est utilisée
							//On sauvegarde le i dans le Pi qui est inutilisé pour les Apériodiques
							listeTacheAp.get(a).setPi(i);
						}
						listeTacheAp.get(a).setDuree(listeTacheAp.get(a).getDuree()-1);
						if(listeTacheAp.get(a).getDuree() == 0)
						{
							listeTacheAp.get(a).setActive(false);
							//Derniere fois que la tache est utilisée
							//On sauvegarde le i dans le Di qui est inutilisée pour les Apériodiques
							listeTacheAp.get(a).setDi(i);
						}
						tache = listeTacheP.size()+a;
						find = true;
						//On vérifie si la tache précédente était une tache périodique
						if(isPeriodique)
						{
							nbChangementContexte++;
							isPeriodique = false;
						}
					}
				}
			}
			listeKiwi.add(""+tache);
			if(i!=0)
			{
				if(tachePrecedente != tache)
				{	
					//On vérifie les préemptions
					if(tachePrecedenteActive)
					{
						nbPreemption++;
					}
				}
			}
			//On met a jour la tache précédente avec la tache actuelle.
			tachePrecedente = tache;
			if(tache != -1)
			{
				if(tache>listeTacheP.size()-1){
					tachePrecedenteActive = listeTacheAp.get(tache-listeTacheP.size()).getActive();
				}
				else
				{
					tachePrecedenteActive = listeTacheP.get(tache).getActive();
				}
			}
			else
			{
				tachePrecedenteActive = false;
			}
			//On vérifie si toutes les taches Apériodiques ont été effectués
			if(listeTacheAp.size()>0 && !allApDone)
			{
				boolean allDone = true;
				for(int b = 0; b<listeTacheAp.size(); b++)
				{
					if(listeTacheAp.get(b).getDuree() != 0)
					{
						allDone = false;
					}
				}
				if(allDone)
				{
					System.out.println("Toutes les taches Ap ont été exécutées.");
					allApDone = true;
				}
			}
			if(allApDone)
			{
				compteur--;
			}
			
			i++;
		}
		int borneMin = Integer.MAX_VALUE;
		int borneMax = 0;
		int moyenne = 0;
		//On regarde si toutes les taches apériodiques ont été effectués
		for(int a = 0;a<listeTacheAp.size(); a++)
		{
			if(listeTacheAp.get(a).getDuree() != 0)
			{
				nbViolationEcheance++;
			}
			int temps = listeTacheAp.get(a).getDi() - listeTacheAp.get(a).getPi();
			if(temps < borneMin)
			{
				borneMin = temps;
			}
			if(temps > borneMax)
			{
				borneMax = temps;
			}
			moyenne += temps;
		}
		if(listeTacheAp.size()>0)
		{
			moyenne = moyenne/listeTacheAp.size();
		}
		duration = i;
		ManipulationFichier<IGenerationTache> outils = new ManipulationFichier<IGenerationTache>();
		outils.sauvegardePerformance("rmbg", borneMin, borneMax, moyenne, nbPreemption, nbChangementContexte, nbViolationEcheance);
		System.out.println("Fin du calcul de l'ordonnancement.");
	}
	
	/**
	 * Permet de recuperer la liste necessaire au parseur du fichier kiwi
	 * @return la liste necessaire au parseur du fichier kiwi
	 */
	public ArrayList<String> getListeKiwi() {
		return listeKiwi;
	}

	/**
	 * Permet de recuperer la duree (en pas) de l'ordonnancement
	 * @return la duration necessaire au fichier kiwi
	 */
	public int getDuration()
	{
		return duration;
	}
	
}
